#include "includes.h"


// Tests for functions in dominion.c



void test_buyCard(void)
{
    // int buyCard(int supplyPos, struct gameState *state) {
    // coins/cost, buys, phase, supply

    int nPlayers = 2;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    for (enum CARD card = curse; card <= treasure_map; card++)
    {
        int cost = cards[card].cost;
        for (int ncoins = 0; ncoins < cost + 1; ncoins++)
        {
            for (int buys = 0; buys < 10; buys++)
            {
                for (int cardsupply = -1; cardsupply < 5; cardsupply++)
                {
                    gs->coins = ncoins;
                    gs->numBuys = buys;
                    gs->supplyCount[card] = cardsupply;
                    int result = buyCard(card, gs);
                    if ((buys > 0) && (ncoins >= cost) && (cardsupply > 0))
                    {
                        CU_ASSERT(result == 0);
                        CU_ASSERT(isValidState(gs));
                        CU_ASSERT(gs->supplyCount[card] == cardsupply - 1);
                        CU_ASSERT(gs->numBuys == buys - 1);
                        if (cost == 0)
                        {
                            CU_ASSERT(gs->coins == 0);
                        }
                        else
                        {
                            CU_ASSERT(gs->coins == ncoins - cards[card].cost);
                        }
                        // ****** TODO check that card has been added to discard pile
                    }
                    else
                    {
                        CU_ASSERT(result == -1);
                    }
                }
            }
        }
    }
    free(gs);
}

void test_cardEffect(void)
// Tests for individual cards are in cardtests.c
{
    int nPlayers = 2;
    int player = 0;
    for (enum CARD card = 0; card <= treasure_map; card++)
    {
        for (int handPos = 0; handPos < 9; handPos++)
        {
            struct gameState *gs = setup(NULL, NULL, &nPlayers);
            enum CARD cardsInHand[] = {adventurer, baron, copper, estate, feast, gardens, minion, remodel, smithy};
            cardsInHand[handPos] = card;
            int ncards = sizeof(cardsInHand) / sizeof(enum CARD);
            setHand(player, ncards, cardsInHand, gs);
            int choice1 = 0;
            int choice2 = 0;
            int choice3 = 0;
            int bonus = 0;
            // printf("%s\n", cards[card].name);
            cardEffect(card, choice1, choice2, choice3, gs, handPos, &bonus);
            CU_ASSERT(isValidState(gs));
            free(gs);
        }
    }
}

void test_compare(void)
// tests compare function from dominion.c   int compare(const void* a, const void* b)
// compare() is called only by shuffle, with int arguments, so test with ints
{
    // test with a and b positive, zero, and negative
    // and a < b, a == b, and a > b
    for (int a = -3; a < 4; a++)
    {
        for (int b = -3; b < 4; b++)
        {
            CU_ASSERT(compare(&a, &b) == (a > b) - (a < b));
        }
    }
    // test cases where arguments are MIN_INT or MAX_INT for over/underflow *************************************
    int a = INT_MIN;
    int b = 1;
    CU_ASSERT(compare(&a, &b) == -1 );
    CU_ASSERT(compare(&a, &a) == 0 );
    CU_ASSERT(compare(&b, &a) == 1 );
    a = INT_MAX; b = 1;
    CU_ASSERT(compare(&a, &b) == 1 );
    CU_ASSERT(compare(&a, &a) == 0 );
    CU_ASSERT(compare(&b, &a) == -1 );
}

void test_discardCard(void)
// int discardCard(int handPos, int currentPlayer, struct gameState *state, int trashFlag)
{
    int nPlayers = 2;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    int player = 0;
    enum CARD cardsInHand[] = {adventurer, baron, copper, estate, feast, gardens, minion, remodel, smithy};
    int ncards = sizeof(cardsInHand) / sizeof(enum CARD);
    int trash = 0;
    setHand(player, ncards, cardsInHand, gs);
    // showHand(player, gs);

    discardCard(0, player, gs, trash);
    // showHand(player, gs);

    CU_ASSERT(isValidState(gs));
    CU_ASSERT(gs->handCount[player] == ncards - 1);

    // ****** TODO finish ******

}

void test_drawCard(void)
{
    // int drawCard(int player, struct gameState *state)

    int nPlayers = 2;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    int player = 0;

    enum CARD cardsInDeck[] = {adventurer, baron, copper, estate, feast, gardens, minion, remodel, smithy};
    int ncards = sizeof(cardsInDeck) / sizeof(enum CARD);
    setDeck(player, ncards, cardsInDeck, gs);

    enum CARD cardsInDiscard[] = {adventurer, baron, copper, estate, feast, gardens, minion, remodel, smithy};
    ncards = sizeof(cardsInDiscard) / sizeof(enum CARD);
    setDiscard(player, ncards, cardsInDiscard, gs);

    enum CARD cardsInHand[] = {adventurer, baron, copper, estate, feast, gardens, minion, remodel, smithy};
    ncards = sizeof(cardsInHand) / sizeof(enum CARD);
    setHand(player, ncards, cardsInHand, gs);

    drawCard(player, gs);
    CU_ASSERT (isValidState(gs));
    // ******TODO finish ******


}

void test_endTurn(void)
{
    for (int nPlayers = 2; nPlayers < MAX_PLAYERS; nPlayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nPlayers);
        for (int i = 0; i < 12; i++)
        {
            int currPlayer = gs->whoseTurn;
            int nextPlayer = (currPlayer + 1) % nPlayers;
            int result = endTurn(gs);
            CU_ASSERT(result == 0);
            CU_ASSERT(gs->whoseTurn == nextPlayer);
            CU_ASSERT(isValidState(gs));
        }
        free(gs);
    }
}

void test_fullDeckCount(void)
{
    int nPlayers = 3;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    int player = 1;
    int count = 0;
    enum CARD card = adventurer;

    for (int i = 0; i < gs->handCount[player]; i++)
    {
        if (gs->hand[player][i] == card)
        {
            count++;
        }
    }
    for (int i = 0; i < gs->deckCount[player]; i++)
    {
        if (gs->deck[player][i] == card)
        {
            count++;
        }
    }

    for (int i = 0; i < gs->discardCount[player]; i++)
    {
        if (gs->discard[player][i] == card)
        {
            count++;
        }
    }
    CU_ASSERT(count == fullDeckCount(player, card, gs));

    free(gs);
}

void test_gainCard(void)
{
    // int gainCard(int supplyPos, struct gameState *state, int toFlag, int player)

    for (int nplayers = 2; nplayers < MAX_PLAYERS; nplayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nplayers);
        for (int rep = 0; rep < 1000; rep++)
        {
            for (int player = 0; player < nplayers; ++player)
            {
                randCards(gs);
                // toFlag             = 0 : add to discard
                // toFlag             = 1 : add to deck
                // toFlag             = 2 : add to hand
                int toFlag            = randomRange(0, 2);
                int pos               = randomRange(curse, treasure_map);
                int priorDiscardCount = gs->discardCount[player];
                int priorDeckCount    = gs->deckCount[player];
                int priorHandCount    = gs->handCount[player];
                bool haveSupply = gs->supplyCount[pos] > 0;
                int result = gainCard(pos, gs, toFlag, player);

                if (haveSupply)
                {
                    CU_ASSERT(result == 0);

                    switch (toFlag)
                    {
                    case 0:
                        CU_ASSERT(gs->discardCount[player] == priorDiscardCount + 1);
                        break;
                    case 1:
                        CU_ASSERT(gs->deckCount[player] == priorDeckCount + 1);
                        break;
                    case 2:
                        CU_ASSERT(gs->handCount[player] == priorHandCount + 1);
                        break;
                    }
                }
                else
                {
                    CU_ASSERT(result == -1);
                }
            }
        }
        free(gs);
    }
}

void test_getCost(void)
{
    for (enum CARD c = curse; c <= treasure_map; c++)
    {
        CU_ASSERT(getCost(c) == cards[c].cost);
    }
}

// void test_getWinners(void)
// {
//     // CU_ASSERT(getWinners() == );
// }

void test_handCard(void)
{
    int nPlayers = 2;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    for (int player = 0; player < nPlayers; player++)
    {
        gs->whoseTurn = player;
        enum CARD cardsInHand[] = {adventurer, baron, copper, estate, feast, gardens, minion, remodel, smithy};
        int ncards = sizeof(cardsInHand) / sizeof(enum CARD);
        setHand(player, ncards, cardsInHand, gs);
        for (int pos = 0; pos < ncards; pos++)
        {
            CU_ASSERT(handCard(pos, gs) == cardsInHand[pos]);
        }
    }
}



// int initializeGame(int numPlayers, int kingdomCards[10], int randomSeed,
//            struct gameState *state)
void test_initializeGame(void)
{
    struct gameState  *gs = newGame();
    CU_ASSERT_PTR_NOT_NULL(gs);

    int kc[] = {gardens, mine, remodel, smithy, village, baron, great_hall, minion, steward, tribute};
    int randomSeed = 1;

    // test with invalid number of players
    CU_ASSERT (initializeGame(1, kc, randomSeed, gs) == -1);
    CU_ASSERT (initializeGame(MAX_PLAYERS + 1, kc, randomSeed, gs) == -1);


    for (int nPlayers = 2; nPlayers <= MAX_PLAYERS; nPlayers++)
    {
        // ****** TODO generate different combinations of kingdom cards ******

        CU_ASSERT(initializeGame(nPlayers, kc, randomSeed, gs) == 0);

        CU_ASSERT(gs->numPlayers == nPlayers);

        int targetSupplyCount[] = {[curse ... treasure_map] = -1};

        // set number of curse cards
        switch (nPlayers)
        {
        case 2:
            targetSupplyCount[curse] = 10;
            break;
        case 3:
            targetSupplyCount[curse] = 20;
            break;
        case 4:
            targetSupplyCount[curse] = 30;
            break;
        default:
            CU_FAIL("Invalid number of players");
        }

        // set number of treasure cards
        // Each player takes 7 coppers, remainder goes in supply pile
        targetSupplyCount[copper] = 60 - nPlayers * 7;
        targetSupplyCount[silver] = 40;
        targetSupplyCount[gold] = 30;

        // set number of Victory cards
        // 12 of each Victory card (Estate, Duchy, & Province) go in supply
        // except when only 2 players--then 8 of each go in supply
        int nvc = (nPlayers == 2) ? 8 : 12;
        targetSupplyCount[estate] = nvc;
        targetSupplyCount[duchy] = nvc;
        targetSupplyCount[province] = nvc;

        // Kingdom cards: 10 of each in kc[]
        // except Kingdom Victory cards are 8 of each for 2 players, 12 for 3 or 4 players
        for (int i = 0; i < sizeof(kc) / sizeof(kc[0]); i++)
        {
            if (cards[kc[i]].victory)
            {
                targetSupplyCount[kc[i]] = (nPlayers == 2) ? 8 : 12;
            }
            else
            {
                targetSupplyCount[kc[i]] = 10;
            }
        }


        CU_ASSERT( memcmp(gs->supplyCount, targetSupplyCount, sizeof(targetSupplyCount)) == 0)

        // test that embargo token count is 0 for all supply piles
        for (int i = curse; i <= treasure_map; i++)
        {
            CU_ASSERT (gs->embargoTokens[i] == 0);
        }

        // test outpost Played
        CU_ASSERT (gs->outpostPlayed == 0);

        // outpostTurn seems to be unused--no test

        CU_ASSERT(gs->whoseTurn == 0);
        CU_ASSERT(gs->phase == 0);

        CU_ASSERT (gs->numActions == 1);

        CU_ASSERT(checkHandsAndDecks(gs));

        for (int p = 0; p < nPlayers; p++)
        {
            CU_ASSERT(gs->discardCount[p] == 0);
        }
        CU_ASSERT(gs->playedCardCount == 0);
    }


    free(gs);
}


void test_isGameOver(void)
{
    // Game over if either
    // Province card supply pile is empty
    // or any 3 supply piles are empty

    for (int nPlayers = 0; nPlayers < MAX_PLAYERS; nPlayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nPlayers);

        // province empty
        setSupplyCount(province, 0, gs);
        CU_ASSERT (isGameOver(gs));

        // three empty supply piles not including first and last
        setSupplyCount(province, 1, gs);
        setSupplyCount(estate, 0, gs);
        setSupplyCount(duchy, 0, gs);
        setSupplyCount(silver, 0, gs);

        CU_ASSERT (isGameOver(gs));

        // restore supply piles
        setSupplyCount(estate, 1, gs);
        setSupplyCount(duchy, 1, gs);
        setSupplyCount(silver, 1, gs);


        // three empty supply piles including first
        setSupplyCount(province, 1, gs);
        setSupplyCount(curse, 0, gs);
        setSupplyCount(silver, 0, gs);
        setSupplyCount(estate, 0, gs);

        CU_ASSERT (isGameOver(gs));

        // restore supply piles
        setSupplyCount(curse, 1, gs);
        setSupplyCount(silver, 1, gs);
        setSupplyCount(estate, 1, gs);

        // three empty supply piles including last
        setSupplyCount(province, 1, gs);
        setSupplyCount(treasure_map, 0, gs);
        setSupplyCount(silver, 0, gs);
        setSupplyCount(estate, 0, gs);

        CU_ASSERT (isGameOver(gs));

        // restore supply piles
        setSupplyCount(treasure_map, 1, gs);
        setSupplyCount(silver, 1, gs);
        setSupplyCount(estate, 1, gs);

        free(gs);
    }
}

void test_kingdomCards(void)
{
    int td[] = {gardens, mine, remodel, smithy, village, baron, great_hall, minion, steward, tribute};
    int *kc = kingdomCards(td[0], td[1], td[2], td[3], td[4], td[5], td[6], td[7], td[8], td[9]);
    CU_ASSERT_PTR_NOT_NULL(kc);
    for (int i = 0; i < sizeof(td) / sizeof(int); i++)
    {
        CU_ASSERT (kc[i] == td[i]);
    }
}

void test_newGame(void)
{
    struct gameState  *gs = newGame();
    CU_ASSERT_PTR_NOT_NULL(gs);
    free(gs);
}

void test_numHandCards(void)
{
    for (int nPlayers = 0; nPlayers < MAX_PLAYERS; nPlayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nPlayers);
        for (int p = 0; p < nPlayers; p++)
        {
            gs->whoseTurn = p;
            for (int nc = 0; nc < MAX_HAND; nc++)
            {
                gs->handCount[p] = nc;
                CU_ASSERT(numHandCards(gs) == nc);
            }
        }
        free(gs);
    }
}

void test_playCard(void)
{
    int nPlayers = 2;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    int choice1 = 0;
    int choice2 = 0;
    int choice3 = 0;
    for (enum CARD card = curse; card <= treasure_map; card ++)
    {
        setSupplyCount(card, 1, gs);
        int player = 1;
        int pos = 0;
        gs->hand[player][pos] = card;
        gs->whoseTurn = player;

        playCard(pos, choice1, choice2, choice3, gs);

        CU_ASSERT (isValidState(gs));
    }
    free(gs);
}

void test_scoreFor(void)
{

    for (int nplayers = 2; nplayers < MAX_PLAYERS; nplayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nplayers);
        for (int rep = 0; rep < 1000; rep++)
        {
            randCards(gs);
            CU_ASSERT(isValidState(gs));
            for (int player = 0; player < nplayers; player++)
            {
                if (scoreFor(player, gs) != getScore(player, gs))
                {
                    showHand(player, gs);
                    showDeck(player, gs);
                    showDiscard(player, gs);
                    printf("Score: %i, %i\n\n", scoreFor(player, gs), getScore(player, gs) );
                }
                CU_ASSERT(scoreFor(player, gs) == getScore(player, gs));
            }
        }
        free(gs);
    }

}

void test_shuffle(void)
{
    // CU_ASSERT(shuffle() == );
}

void test_supplyCount(void)
{
    int nPlayers = 2;
    struct gameState *gs = setup(NULL, NULL, &nPlayers);
    for (enum CARD c = curse; c <= treasure_map; c++)
    {
        for (int n = 0; n < 20; n++)
        {
            setSupplyCount(c, n, gs);
            CU_ASSERT(gs->supplyCount[c] == supplyCount(c, gs));
        }
    }
    free(gs);
}

void test_updateCoins(void)
{
    // int updateCoins(int player, struct gameState *state, int bonus)

    for (int nPlayers = 2; nPlayers < MAX_PLAYERS; nPlayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nPlayers);

        randCards(gs);
        for (int player = 0; player < MAX_PLAYERS; ++player)
        {
            for (int bonus = 0; bonus < 10; ++bonus)
            {
                int coinCount = 0;
                for (int i = 0; i < gs->handCount[player]; i++)
                {
                    enum CARD card = gs->hand[player][i];
                    if (isTreasureCard(card))
                    {
                        coinCount += cards[card].value;
                    }
                }
                coinCount += bonus;
                updateCoins(player, gs, bonus);
                CU_ASSERT(coinCount == gs->coins);
            }

        }
    }

}

void test_whoseTurn(void)
{
    for (int nPlayers = 0; nPlayers < MAX_PLAYERS; nPlayers++)
    {
        struct gameState *gs = setup(NULL, NULL, &nPlayers);
        for (int p = 0; p < nPlayers; p++)
        {
            gs->whoseTurn = p;
            CU_ASSERT(whoseTurn(gs) == p);
        }
        free(gs);
    }
}





